#include <os/driver.h>
#include <os/memcache.h>
#include <os/initcall.h>
#include <os/debug.h>
#include <os/virmem.h>
#include <os/hardirq.h>
#include <arch/pci.h>
#include <lib/stddef.h>
#include <lib/type.h>
#include <sys/res.h>
#include <driver/usb.h>
#include <driver/usb/hub.h>

const char *usb_hub_type_name[4] = {"UHCI", "OHCI", "EHCI", "XHCI"};

static usb_bus_t *usb_bus = NULL;

static int HubFindFromPci(device_extension_t *extension)
{
    pci_dev_t *dev = PciGetDeviceByClass(USB_CONTROLLER_CLASS, USB_CONTROLLER_SUBCLASS);
    if (!dev)
    {
        KPrint("[usb] no find any usb controller device on pci bus!\n");
        return -1;
    }
    // try to find usb 2.0 controller
    if (dev->proIF == USB_OHCI_CONTROLLER_PROIF)
    {
        dev = PciGetDeviceByClassAndProIF(USB_CONTROLLER_CLASS, USB_CONTROLLER_SUBCLASS, USB_EHCI_CONTROLLER_PROIF);
    }
    KPrint("[usb] usb controller was found! bus=%d dev=%d func=%d\n", dev->bus, dev->dev, dev->fun);
    KPrint("[usb] usb controller proIF:0x%x irq %d\n", dev->proIF, dev->irq);

    // enable bus master
    PciEnableBusMaster(dev);

    extension->irq = dev->irq;
    extension->pci_device = dev;
    extension->iobase = PciDeviceGetMemAddr(dev);
    extension->iolen = PciDeviceGetMemLen(dev);
    if (!extension->iobase)
    {
        KPrint("[usb] get mem map address failed!\n");
        return -1;
    }
    extension->iobase = MemIoReMap(extension->iobase, extension->iolen);
    if (!extension->iobase)
    {
        KPrint("[usb] io address remap failed!\n");
        return -1;
    }
    KPrint("[usb] io address remap to virbase %x\n", extension->iobase);

    if (dev->proIF == USB_UHCI_CONTROLLER_PROIF)
    {
        extension->usb_type = USB_TYPE_UHCI;
    }
    else if (dev->proIF == USB_OHCI_CONTROLLER_PROIF)
    {
        extension->usb_type = USB_TYPE_OHCI;
    }
    else if (dev->proIF == USB_EHCI_CONTROLLER_PROIF)
    {
        extension->usb_type = USB_TYPE_EHCI;
    }
    else
    {
        extension->usb_type = USB_TYPE_XHCI;
    }
    KPrint("[usb] usb controller type: %s iobase %x len %d\n", usb_hub_type_name[extension->usb_type], extension->iobase, extension->iolen);
    return 0;
}

static int HubInit(device_extension_t *extension)
{
    // find usb controller
    if (HubFindFromPci(extension) < 0)
    {
        KPrint('[usb] init failed! reason: no found controller\n');
        return -1;
    }
    // according hub type to switch
    if (extension->usb_type == USB_TYPE_UHCI)
    {
        extension->hub_port.UHCI.usb_command = 0x00;
        extension->hub_port.UHCI.usb_status = 0x02;
        extension->hub_port.UHCI.usb_interrupt_enable = 0x04;
        extension->hub_port.UHCI.frame_number = 0x06;
        extension->hub_port.UHCI.frame_list_base = 0x08;
        extension->hub_port.UHCI.start_of_frame = 0x0C;
        extension->hub_port.UHCI.port1_status = 0x10;
        extension->hub_port.UHCI.port2_status = 0x12;
    }
    else
    {
        if (extension->usb_type == USB_TYPE_OHCI)
        {
        }
        else
        {
            if (extension->usb_type == USB_TYPE_EHCI)
            {
            }
            else
            {
                if (extension->usb_type == USB_TYPE_XHCI)
                {
                    extension->hub_port.XHCI.cap_reg_len = 0x00;
                    extension->hub_port.XHCI.reserved = 0x01;
                    extension->hub_port.XHCI.interface_ver_num = 0x02;
                    extension->hub_port.XHCI.struct_param1 = 0x04;
                    extension->hub_port.XHCI.struct_param2 = 0x08;
                    extension->hub_port.XHCI.struct_param3 = 0x0C;
                    extension->hub_port.XHCI.cap_param1 = 0x10;
                    extension->hub_port.XHCI.doorbell_off = 0x14;
                    extension->hub_port.XHCI.run_reg_space_off = 0x18;
                    extension->hub_port.XHCI.cap_param2 = 0x1C;
                    uint32_t op_reg_base = *(uint8_t *)(extension->iobase + extension->hub_port.XHCI.cap_reg_len);
                    extension->hub_port.XHCI.usb_command = op_reg_base + 0x00;
                    extension->hub_port.XHCI.usb_status = op_reg_base + 0x04;
                    extension->hub_port.XHCI.page_size = op_reg_base + 0x08;
                    extension->hub_port.XHCI.device_notific_control = op_reg_base + 0x14;
                    extension->hub_port.XHCI.command_ring_ctrl = op_reg_base + 0x18;
                    extension->hub_port.XHCI.device_context_base_array = op_reg_base + 0x30;
                    extension->hub_port.XHCI.configure = op_reg_base + 0x38;
                    extension->hub_port.XHCI.port_base = op_reg_base + 0x400;
                }
            }
        }
    }
    return 0;
}

static usb_bus_t *UsbBusAlloc(device_object_t *device)
{
    usb_bus = (usb_bus_t *)KMemAlloc(sizeof(usb_bus_t));
    if (!usb_bus)
    {
        return NULL;
    }
    memset(usb_bus->bus_name, 0, DEVICE_NAME_LEN);
    strcpy(usb_bus->bus_name, device->name.text);
    usb_bus->bus = 0;
    return usb_bus;
}

static usb_device_t *UsbDeviceAlloc(device_object_t *device, uint32_t port)
{
    usb_device_t *usb_dev = (usb_device_t *)KMemAlloc(sizeof(usb_device_t));
    if (!device)
    {
        KPrint("[usb] device object for port %d alloc failed!\n", port);
        return NULL;
    }
    usb_dev->port = port;
}

static uint32_t UsbRegRead32(device_extension_t *extension, uint32_t off)
{
    return *(uint32_t *)(extension->iobase + off);
}

static uint16_t UsbRegRead16(device_extension_t *extension, uint32_t off)
{
    return *(uint16_t *)(extension->iobase + off);
}

static uint8_t UsbRegRead8(device_extension_t *extension, uint32_t off)
{
    return *(uint8_t *)(extension->iobase + off);
}

static void UsbRegWrite32(device_extension_t *extension, uint32_t off, uint32_t data)
{
    *(uint32_t *)(extension->iobase + off) = data;
}

static void UsbRegWrite16(device_extension_t *extension, uint32_t off, uint16_t data)
{
    *(uint16_t *)(extension->iobase + off) = data;
}

static void UsbRegWrite8(device_extension_t *extension, uint32_t off, uint8_t data)
{
    *(uint8_t *)(extension->iobase + off) = data;
}

static usb_device_t *UsbHubProbes(device_extension_t *extension)
{
    uint32_t iobase = extension->iobase;
    uint32_t len = extension->iolen;

    int port = 0, max_port;

    KPrint("[usb] probe usb hub ports!\n");
    if (extension->usb_type == USB_TYPE_UHCI)
    {
        max_port = 2;
        // enum hub port,front 16 address are used to controller,every device have two ports
        for (port = 0; port < max_port; port++)
        {
            uint16_t status = *(uint16_t *)(extension->iobase + extension->hub_port.UHCI.port1_status + (port * 2));
            KPrint("[usb] hub status %x\n", (uint32_t)status);
            // bits 7 always be set
            if (!(status & 0x0080))
                break;
        }
    }
    else
    {
        if (extension->usb_type == USB_TYPE_XHCI)
        {
            max_port = ((*(uint32_t *)(extension->iobase + extension->hub_port.XHCI.struct_param1)) >> 24) & 0xff;
            KPrint("[usb] max port %d\n", max_port);
            // enum hub port,from 0x400 start
            for (port = 0; port < max_port; port++)
            {
                uint32_t status = *(uint32_t *)(extension->iobase + extension->hub_port.XHCI.port_base + (port * 16));
                // bits 9 always be set
                if (!(status & 0x00000200))
                    break;
            }
        }
    }
    KPrint("[usb] hub found %d ports!\n", port);

    if (extension->usb_type == USB_TYPE_XHCI)
    {
        // enable XHCI to run
        UsbRegWrite32(extension, extension->hub_port.XHCI.usb_command, UsbRegRead32(extension, extension->hub_port.XHCI.usb_command) | USB_XHCI_CMD_REG_RUNSTOP);
        if (!(UsbRegRead32(extension, extension->hub_port.XHCI.usb_status) & 0x01))
        {
            KPrint("[usb] XHCI enable!\n");
        }
        UsbRegWrite32(extension, extension->hub_port.XHCI.usb_command, UsbRegRead32(extension, extension->hub_port.XHCI.usb_command) | USB_XHCI_CMD_REG_HUBRESET);
        KPrint("[usb] XHCI reset hub\n");

        for (int i = 0; i < port; i++)
        {
            UsbRegWrite32(extension, extension->hub_port.XHCI.port_base + (i * 16), USB_XHCI_PORT_STATUS_RESET | USB_XHCI_PORT_STATUS_POWER);
            uint32_t status = UsbRegRead32(extension, extension->hub_port.XHCI.port_base + (i * 16));
            KPrint("[usb] read status %x\n", status);
        }
    }

    // create usb device
    /*for (int i = 0; i < max_port; i++)
    {
        usb_device_t *usb_device = UsbDeviceAlloc(i);
        if (!usb_device)
        {
            KPrint("[usb] usb device alloc failed!\n");
            return -1;
        }
        UsbDeviceRegister(usb_device);
    }*/
}

static iostatus_t UsbEnter(driver_object_t *driver)
{
    device_object_t *device;
    iostatus_t status = IO_SUCCESS;
    device_extension_t *extension = NULL;

    status = IoCreateDevice(driver, sizeof(device_extension_t), DEVICE_NAME, DEVICE_TYPE_STREAM, &device);
    if (status != IO_SUCCESS)
    {
        KPrint("[driver] create device %s failed!\n", DEVICE_NAME);
        status = IO_FAILED;
        return status;
    }
    // neither io mode
    device->flags = 0;
    extension = device->device_extension;

    // init hub
    if (HubInit(extension) < 0)
    {
        status = IO_FAILED;
        IoDeleteDevice(device);
        return status;
    }

    // alloc usb bus
    usb_bus = UsbBusAlloc(device);
    // register usb bus
    extension->bus = usb_bus;
    // probe hub ports
    UsbHubProbes(extension);
    // alloc usb device
    // register usb controller irq
    // IrqRegister(extension->irq, UsbHubHandler, IRQ_DISABLE, "usb-hub", "hub", extension);
    // get device info

    // get device descriptor

    // alloc device address

    return status;
}

static iostatus_t UsbExit(driver_object_t *driver)
{
}

static iostatus_t UsbOpen(driver_object_t *device, io_request_t *ioreq)
{
}

static iostatus_t UsbClose(driver_object_t *device, io_request_t *ioreq)
{
}

static iostatus_t UsbRead(device_object_t *device, io_request_t *ioreq)
{
}

static iostatus_t UsbWrite(device_object_t *device, io_request_t *ioreq)
{
}

static iostatus_t UsbDevCtl(device_object_t *device, io_request_t *ioreq)
{
}

static iostatus_t UsbDriverFunc(driver_object_t *driver)
{
    string_new(&driver->name, DRIVER_NAME, DRIVER_NAME_LEN);

    driver->driver_enter = UsbEnter;
    driver->driver_exit = UsbExit;

    driver->dispatch_fun[IOREQ_OPEN] = UsbOpen;
    driver->dispatch_fun[IOREQ_CLOSE] = UsbClose;
    driver->dispatch_fun[IOREQ_READ] = UsbRead;
    driver->dispatch_fun[IOREQ_WRITE] = UsbWrite;
    driver->dispatch_fun[IOREQ_DEVCTL] = UsbDevCtl;

    return IO_SUCCESS;
}

static __init void UsbDriverEntry()
{
    KPrint("[driver] create usb driver\n");
    /*if (DriverObjectCreate(UsbDriverFunc) < 0)
    {
        KPrint("[driver] create usb driver failed!\n");
    }*/
}

driver_initcall(UsbDriverEntry);